import { describe, expect, it } from 'vitest';

import { AxMemory } from '../mem/memory.js';

import {
  processFieldProcessors,
  processStreamingFieldProcessors,
} from './fieldProcessor.js';
import type { AxFieldValue, AxGenOut } from './types.js';

describe('Field Processor Functions', () => {
  it('processFieldProcessors should execute the processor and update memory', async () => {
    // Dummy synchronous processor: converts the value to uppercase.
    const dummyProcessor = {
      field: {
        name: 'testField',
        title: 'testField',
        type: { name: 'string' as const, isArray: false },
      },
      process: async (value: AxFieldValue) => {
        if (typeof value === 'string') {
          return value.toUpperCase();
        }
        return value;
      },
    };

    const values: AxGenOut = { testField: 'hello world' };
    const mem = new AxMemory();
    const sessionId = 'session-sync';

    await processFieldProcessors([dummyProcessor], values, mem, sessionId);

    // The processor no longer updates the 'values' object directly.
    // Instead, we check that memory has been updated.
    const history = mem.history(0, sessionId);
    expect(history.length).toBe(1);

    // The message text is generated by addToMemory and will include the processed string.
    // We use toContain() to check that "HELLO WORLD" appears.
    const lastEntry = mem.getLast(sessionId);
    expect(lastEntry?.tags).toContain('processor');
    expect(lastEntry?.role).toBe('user');

    // The content is stored in the chat array
    const userMessage = history[0];
    expect(userMessage?.role).toBe('user');
    if (userMessage?.role === 'user' && Array.isArray(userMessage.content)) {
      const textContent = userMessage.content.find((c) => c.type === 'text');
      if (textContent && 'text' in textContent) {
        expect(textContent.text).toContain('HELLO WORLD');
      }
    } else if (
      userMessage?.role === 'user' &&
      typeof userMessage.content === 'string'
    ) {
      expect(userMessage.content).toContain('HELLO WORLD');
    }
  });

  it('processStreamingFieldProcessors should execute the processor and update memory without yielding any values', async () => {
    // Dummy streaming processor: appends a suffix.
    const dummyStreamingProcessor = {
      field: {
        name: 'streamField',
        title: 'streamField',
        type: { name: 'string' as const, isArray: false },
      },
      process: async (value: unknown) => {
        if (typeof value === 'string') {
          return `${value} updated`;
        }
        return value;
      },
    };

    const values: AxGenOut = { streamField: 'original' };
    const mem = new AxMemory();
    const sessionId = 'session-stream';
    // Create an extraction state with the current field set and start index 0.
    const xstate = {
      currField: dummyStreamingProcessor.field,
      s: 0,
      extractedFields: [],
      streamedIndex: {},
    };

    // Provide an initial content string.
    await processStreamingFieldProcessors(
      [dummyStreamingProcessor],
      'original',
      xstate,
      mem,
      values,
      sessionId,
      false
    );

    // Check that an assistant message is added into the session's history.
    const history = mem.history(0, sessionId);
    expect(history.length).toBe(1);

    const lastEntry = mem.getLast(sessionId);
    expect(lastEntry?.tags).toContain('processor');
    expect(lastEntry?.role).toBe('user');

    // Check the content in the history
    const userMessage = history[0];
    expect(userMessage?.role).toBe('user');
    if (userMessage?.role === 'user' && Array.isArray(userMessage.content)) {
      const textContent = userMessage.content.find((c) => c.type === 'text');
      if (textContent && 'text' in textContent) {
        expect(textContent.text).toContain('original updated');
      }
    } else if (
      userMessage?.role === 'user' &&
      typeof userMessage.content === 'string'
    ) {
      expect(userMessage.content).toContain('original updated');
    }
  });
});
